package com.es.spring.route;

import com.es.spring.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author 二十
 * @since 2021/11/9 5:31 下午
 */
public class DispatcherServlet extends HttpServlet {
    /**
     * 存放的是 创建好的单实例bean对象
     */
    private Map<String, Object> ioc = new ConcurrentHashMap<>();
    /**
     * 存放的是  路径  与  方法  的  映射
     */
    private Map<String, Method> handlerMappings = new ConcurrentHashMap<>();
    /**
     * 存放的是所有类的名字
     */
    private List<String> classNames = new CopyOnWriteArrayList<>();
    /**
     * 解析配置文件
     */
    private Properties configContext = new Properties();
    /**
     * 配置文件相关
     */
    private static final String CONFIG_LOCATION = "contextConfigLocation";

    private static final String BASE_PACKAGE = "scanPackage";

    private List<Handler> handlerMapping = new ArrayList<>();


    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件
        loadConfig(config.getInitParameter(CONFIG_LOCATION));
        //2.扫描所有的组件
        doScanPackages(configContext.getProperty(BASE_PACKAGE));
        //3.将组件加入到容器
        refersh();
        //4.属性设值
        population();
        //5.建立方法与路径的映射
        routingAndMapping();
    }


    /**
     * 加载配置文件
     *
     * @param initParameter
     */
    private void loadConfig(String initParameter) {
        InputStream is = getClass().getClassLoader().getResourceAsStream(initParameter);
        try {
            configContext.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 组件扫描，拿到所有的类
     *
     * @param scanPackage
     */
    private void doScanPackages(String scanPackage) {
        URL url = getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        assert url != null;
        File files = new File(url.getFile());
        for (File file : files.listFiles()) {
            if (file.isDirectory()) {
                doScanPackages(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }

    }

    /**
     * 容器刷新，加载所有的单实例bean对象
     */
    private void refersh() {
        if (classNames == null || classNames.isEmpty()) {
            throw new RuntimeException("组件扫描出现异常！");
        }

        try {
            //遍历所有扫描到的类
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                //处理控制器
                if (clazz.isAnnotationPresent(Controller.class)) {
                    ioc.put(clazz.getSimpleName(), clazz.newInstance());
                    //处理service
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    Object instance = clazz.newInstance();
                    ioc.put(clazz.getSimpleName(), instance);
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> arg : interfaces) {
                        ioc.put(arg.getSimpleName(), clazz);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 属性赋值
     */
    private void population() {
        //拿到所有的单实例列表
        Set<String> keySet = ioc.keySet();

        for (String key : keySet) {
            //获取单实例bean的所有字段
            Field[] fields = ioc.get(key).getClass().getFields();
            for (Field field : fields) {
                //如果是自动装配
                if (field.isAnnotationPresent(Autowired.class)) {
                    //获取字段上面的注解
                    Autowired autowired = field.getAnnotation(Autowired.class);
                    //获取注解里面指定的value值
                    String name = autowired.value().trim();
                    //默认值的处理逻辑
                    if ("".equals(autowired.value().trim())) {
                        name = field.getType().getName();
                    }
                    try {
                        //暴力反射
                        field.setAccessible(true);
                        field.set(name, ioc.get(name));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 建立方法与路径的映射关系
     */
    private void routingAndMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(Controller.class)) {
                continue;
            }
            String url = "";
            if (clazz.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                url = requestMapping.value();
            }
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(RequestMapping.class)) {
                    continue;
                }
                RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                String regex = ("/" + url + requestMapping.value()).replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                handlerMapping.add(new Handler(pattern, entry.getValue(), method));
            }
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatcher(req, resp);
        } catch (Exception e) {
            throw new RuntimeException(" 500 server error!");
        }
    }

    /**
     * 路由转发
     *
     * @param req
     * @param resp
     */
    private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
        Handler handler = getHandler(req);

        if (handler == null) {
            throw new RuntimeException("404 Not Found!");
        }

        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();

        Object[] paramValues = new Object[parameterTypes.length];

        Map<String, String[]> params = req.getParameterMap();

        for (Map.Entry<String, String[]> param : params.entrySet()) {
            String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", ",");
            if (!handler.getParamIndexMapping().containsKey(param.getKey())) {
                continue;
            }
            Integer index = handler.getParamIndexMapping().get(param.getKey());
            paramValues[index] = this.convert(parameterTypes[index], value);
        }

        if (handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
            int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
        }
        if (handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
            int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex] = resp;
        }
        try {
            Object returnValue = handler.getMethod().invoke(handler.getController(), paramValues);
            if (returnValue == null || returnValue instanceof Void) {
                return;
            }

            resp.getWriter().write(returnValue.toString());
        } catch (IOException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private Object convert(Class<?> parameterType, String value) {
        if (Integer.class == parameterType) {
            return Integer.parseInt(value);
        }
        return value;
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) {
            return null;
        }
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "")
                .replaceAll("/+", "/");
        for (Handler handler : handlerMapping) {
            try {
                Matcher matcher = handler.pattern.matcher(url);
                //如果没有匹配上继续下一个匹配
                if (!matcher.matches()) {
                    continue;
                }
                return handler;
            } catch (Exception e) {
                throw e;
            }
        }
        return null;
    }


    private class Handler {
        //保存方法对应的实例
        private Object controller;
        //保存映射的方法
        private Method method;
        //正则匹配
        private Pattern pattern;
        //参数顺序
        private Map<String, Integer> paramIndexMapping;

        public Handler(Pattern pattern, Object controller, Method method) {
            this.controller = controller;
            this.method = method;
            this.pattern = pattern;
            paramIndexMapping = new ConcurrentHashMap<String, Integer>();
            putParamIndexMapping(method);
        }

        private void putParamIndexMapping(Method method) {
            //提取方法中加了注解的参数
            Annotation[][] pa = method.getParameterAnnotations();

            for (int i = 0; i < pa.length; i++) {
                for (Annotation a : pa[i]) {
                    if (a instanceof RequestParam) {
                        String paramName = ((RequestParam) a).value();
                        if (!"".equals(paramName.trim())) {
                            paramIndexMapping.put(paramName, i);
                        }
                    }
                }
            }

            //提取方法中的req和resp
            Class<?>[] parameterTypes = method.getParameterTypes();

            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> type = parameterTypes[i];

                if (type == HttpServletRequest.class ||
                        type == HttpServletResponse.class) {
                    paramIndexMapping.put(type.getName(), i);
                }
            }
        }


        public Object getController() {
            return controller;
        }

        public void setController(Object controller) {
            this.controller = controller;
        }

        public Method getMethod() {
            return method;
        }

        public void setMethod(Method method) {
            this.method = method;
        }

        public Pattern getPattern() {
            return pattern;
        }

        public void setPattern(Pattern pattern) {
            this.pattern = pattern;
        }

        public Map<String, Integer> getParamIndexMapping() {
            return paramIndexMapping;
        }

        public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
            this.paramIndexMapping = paramIndexMapping;
        }
    }


}
